import tkinter as tk
import tkinter.messagebox
import tkinter.filedialog
import socket
import winsound
import os
import os.path
import pickle
import time
import threading
import re
import sys

s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
stcp=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
d={}#name:ip
vd={}#ip:name
mslist=[]
#start-ip
btlist=[]
btflag=False
our_name=""
def get_self():
    try:
        s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        s.connect(("www.baidu.com",80))
        return s.getsockname()[0]
    except:
        return None
#start-ip
def loading():
    global our_name,d,mslist,vd
    try:
        with open("chater.cfg",mode="rb") as f:
            listemp=pickle.load(f)
            if not type(listemp)==list and len(listemp)==3:
                return False
            if listemp[0]=="": return False
            our_name=get_self()
            if not our_name : our_name=listemp[0]
            d=listemp[1]
            d["全局广播"]="255.255.255.255"
            vd={v: k for k, v in d.items()}
            mslist=listemp[2]
        return True
    except:
        return False


def dumping(flag=True):
    if not flag:
        with open("chater.cfg",mode="wb") as f:
            pickle.dump([our_name,d,tuple()],f)
        return
    with open("chater.cfg",mode="wb") as f:
        pickle.dump([our_name,d,listm.get(0,"end")],f)


def is_valid_ipv4_address(ip_str):
    sep = ip_str.split('.')
    if len(sep) != 4:
        return False
    for x in sep:
        try:
            int_x = int(x)
            if int_x < 0 or int_x > 255:
                return False
        except ValueError as e:
            return False
    return True

def binding(ip):
    if is_valid_ipv4_address(ip):
        s.bind((ip,8080))
        stcp.bind((ip,8081))
        s.setblocking(0)
        
if not loading():
    our_name=get_self()
    if our_name:
        binding(our_name)
        d={"全局广播":"255.255.255.255"}
        vd={v: k for k, v in d.items()}
        dumping(False)
    else:
        def getsip():
            global our_name,d,vd
            our_name=e.get()
            if is_valid_ipv4_address(our_name):
                try:
                    binding(our_name)
                    d={"全局广播":"255.255.255.255"}
                    vd={v: k for k, v in d.items()}

                except:
                    tkinter.messagebox.showerror("错误","这不是你的IP地址！")
                else:
                    dumping(False)
                    nroot.destroy()
            else:
                tkinter.messagebox.showerror("错误","IP格式错误！")
        nroot=tk.Tk()
        nroot.title("")
        tk.Label(nroot,text="本机IP地址:").grid(row=0,column=0,rowspan=1,columnspan=3)
        e=tk.Entry(nroot)
        e.grid(row=1,column=0,rowspan=1,columnspan=2)
        b=tk.Button(nroot,text="确定",command=getsip)
        b.grid(row=1,column=2,rowspan=1,columnspan=1)
        nroot.mainloop()
else:
    try:
        binding(our_name)
    except:
        tkinter.messagebox.showerror("错误","你可能更换了计算机或IP被刷新。请联网或删除配置文件后重试。")
        sys.exit(0)



wtest=lambda x:"TTL" in os.popen("ping /n 1 "+x).read()
def fr():
    def nd():
        global vd
        name=e1.get()
        cname=e2.get()
        if is_valid_ipv4_address(name):
            if not wtest(name):
                tkinter.messagebox.showwarning("警告","IP未连通，对方可能不在线！")
            listb.insert("end",cname)
            d[cname]=name
            vd={v: k for k, v in d.items()}
            dumping()
            nroot.destroy()
        else:
            tkinter.messagebox.showerror("错误","IP格式错误！")
    nroot=tk.Toplevel()
    nroot.wm_attributes("-topmost", True)
    nroot.title("")
    nroot.geometry("+"+str(root.winfo_x()+50)+"+"+str(root.winfo_y()+50))
    nroot.grab_set()
    
    tk.Label(nroot,text="IP地址:").grid(row=0,column=0,rowspan=1,columnspan=3)
    e1=tk.Entry(nroot)
    e1.grid(row=1,column=0,rowspan=1,columnspan=3)
    
    tk.Label(nroot,text="计算机名（自定义）:").grid(row=2,column=0,rowspan=1,columnspan=3)
    e2=tk.Entry(nroot)
    e2.grid(row=3,column=0,rowspan=1,columnspan=3)
    
    b=tk.Button(nroot,text="确定",command=nd)
    b.grid(row=4,column=0,rowspan=1,columnspan=3)
    nroot.mainloop()
def reload():
    try:
        while True:
            data, addr = s.recvfrom(65536)
            if data.decode("utf-8")=="&return&":
                s.sendto(b"&ok&",addr)
                continue
            if addr[0]==our_name and "[广播]" in data.decode("utf-8"):
                continue
            pushlt(data,addr)
            
    except:
        pass
    finally:
        root.after(250, reload)

def pushlt(data,addr):
    winsound.Beep(1000,500)
    if addr[0] in vd: listm.insert("end", vd[addr[0]] + ":" + data.decode("utf-8"))
    else: listm.insert("end", addr[0] + ":" + data.decode("utf-8"))
    dumping()
def sent(event=None):
    text=e.get()
    try:
        addr=d[listb.get(listb.curselection())]
        e.delete("0","end")
    except:
        tkinter.messagebox.showerror("错误","您似乎没选收件人...")
    else:
        listm.insert("end","我=>"+vd[addr]+":"+text)
        if addr=="255.255.255.255": text="[广播]"+text
        s.sendto(text.encode("utf-8"),(addr,8080))
        dumping()
def dell():
    del d[listb.get("active")]
    vd={v: k for k, v in d.items()}
    listb.delete("active")
    dumping()
def more(flag):

    
    def clitest():
        try:
            addrn=d[listb.get(listb.curselection())]
            assert addrn!="255.255.255.255"
            assert addrn!=our_name
        except AssertionError as e:
            tkinter.messagebox.showerror("错误","您似乎选择了广播或自己...")
            nroot.destroy()
        except:
            tkinter.messagebox.showerror("错误","您似乎没选收件人...")
            nroot.destroy()
        else:
            s.sendto(b"&return&",(addrn,8080))
            oflag=True
            for i in range(4):
                try:
                    data, addr = s.recvfrom(65536)
                except:
                    pass
                else:
                    if data.decode("utf-8")=="&ok&" and addr[0]==addrn:
                        oflag=False
                        break
                    pushlt(data,addr)
                finally:
                    time.sleep(0.1)
            if oflag:
                tkinter.messagebox.showerror("错误","对方似乎没有打开接收端或网络中断")
            else:
                tkinter.messagebox.showinfo("提示","对方接收端工作正常")
            nroot.destroy()

            
    def clearing():
        listm.delete(0,"end")
        mslist=[]
        dumping()
        nroot.destroy()

        
    def resetcast():
        d["全局广播"]="255.255.255.255"
        vd={v: k for k, v in d.items()}
        listb.delete(0,"end")
        for k in d:
            listb.insert("end",k)
        dumping()
        nroot.destroy()

        
    def sender():
        try:
            addr=d[listb.get(listb.curselection())]
            assert addr!="255.255.255.255"
        except AssertionError as e:
            tkinter.messagebox.showerror("错误","不能广播文件")
            nroot.destroy()
        except:
            tkinter.messagebox.showerror("错误","您似乎没选收件人...")
            nroot.destroy()
        else:
            filename=tkinter.filedialog.askopenfilename()
            if filename!="":
                nroot.destroy()
                st=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                st.setblocking(1)
                try:
                    st.connect((addr,8081))
                except:
                    tkinter.messagebox.showerror("错误","连接失败")
                else:
                    st.send(os.path.basename(filename).encode("utf-8"))
                    time.sleep(0.3)
                    with open(filename,mode="rb") as f:
                        while 1:
                            chunk=f.read(1048576)
                            if not chunk: break
                            st.send(chunk)
                    st.close()
                    listm.insert("end","发送成功！")
                    dumping()
    def checker():
        tkinter.messagebox.showinfo("查询结果","你的IP是:"+our_name)
        nroot.destroy()
    def reloadbts():
        for i,k in enumerate(btlist,start=1):
            tk.Button(nroot,text=k[0],command=k[1]).grid(row=i,column=0,rowspan=1,columnspan=3)
    def loadmod(locas):
        tkinter.messagebox.showwarning("提示","请使用正确的模组。")
        filename=tkinter.filedialog.askopenfilename()
        if filename!="":
            nroot.destroy()
            try:
                with open(filename,mode="rb") as f:
                    code=pickle.load(f)
            except:
                tkinter.messagebox.showerror("提示","你知道你做了什么。")
            else:
                exec(code,globals(),locas)
    nroot=tk.Toplevel()
    nroot.wm_attributes("-topmost", True)
    nroot.title("")
    nroot.geometry("+"+str(root.winfo_x()+50)+"+"+str(root.winfo_y()+50))
    nroot.grab_set()
    loc={}
    tk.Label(nroot,text="更多选项").grid(row=0,column=0,rowspan=1,columnspan=3)
    if not flag:
        global btflag
        btlist=[["检查对方客户端运行状态",clitest],
                ["清空消息列表",clearing],
                ["修复广播选项",resetcast],
                ["发送文件",lambda : threading.Thread(target=sender).start()],
                ["查看你的IP",checker],
                ["loadmod",lambda:loadmod(loc)]]
        btflag=True
    loc=locals()
    reloadbts()
    nroot.mainloop()
def helder(c,addr):
    c.setblocking(1)
    c.settimeout(2.5)
    fname=c.recv(1024).decode("utf-8")
    listm.insert("end","正在接收："+fname)
    with open(fname,mode="wb") as f:
        while 1:
            try:
                chunk=c.recv(1048576)
                assert chunk
                f.write(chunk)
            except:
                break
    c.close()
    winsound.Beep(1000,1000)
    try:
        listm.insert("end","您有来自{ip}的新文件！".format(ip=vd[addr[0]]))
    except:
        listm.insert("end","您有来自{ip}的新文件！".format(ip=addr[0]))
    finally:
        dumping()
def receive_f():
    stcp.setblocking(0)
    stcp.listen(1)
    while 1:
        try:
            c,v=stcp.accept()
        except:
            pass
        else:
            threading.Thread(target=helder,args=(c,v)).start()
th1=threading.Thread(target=receive_f)
if sys.version_info.minor>=10:
    th1.daemon=True
else:
    th1.setDaemon(1)
th1.start()
root=tk.Tk()
root.title("Hello Foolish-man聊天器")
root.wm_attributes("-topmost", True)
def exitor():
    root.destroy()
    sys.exit(0)
root.protocol('WM_DELETE_WINDOW',exitor)


root.grid_rowconfigure(0, weight=10)
root.grid_rowconfigure(1, weight=10)
root.grid_rowconfigure(2, weight=10)
root.grid_rowconfigure(3, weight=10)
root.grid_rowconfigure(4, weight=10)
root.grid_rowconfigure(5, weight=10)
root.grid_rowconfigure(6, weight=3)




root.grid_columnconfigure(0, weight=1)
root.grid_columnconfigure(1, weight=1)
root.grid_columnconfigure(2, weight=1)
root.grid_columnconfigure(3, weight=1)
root.grid_columnconfigure(4, weight=1)
root.grid_columnconfigure(5, weight=1)
root.grid_columnconfigure(6, weight=1)



listb=tk.Listbox(root,selectmode=tk.SINGLE)
listb.grid(row=0,column=0,rowspan=6,columnspan=2,sticky="nsew")

buttonins=tk.Button(root,text="添加联系人",command=fr)
buttondel=tk.Button(root,text="删除联系人",command=dell)
# 移动按钮到第6行，避免与listb重叠
buttonins.grid(row=6,column=0,sticky="nsew")
buttondel.grid(row=6,column=1,sticky="nsew")

listm=tk.Listbox(root,width=40)
listm.grid(row=0,column=2,rowspan=6,columnspan=5,sticky="nsew")

e=tk.Entry(root,width=25)
# 移动e到listm不占据的列上，例如第7列
e.grid(row=6,column=2,rowspan=1,columnspan=3,sticky="nsew")
e.bind('<Return>', sent)
buttonsnd=tk.Button(root,text="发送",command=sent)
buttonsnd.grid(row=6,column=5,sticky="nsew")
buttonmor=tk.Button(root,text="更多…",command=lambda :more(btflag))
buttonmor.grid(row=6,column=6,sticky="nsew")
for k in d:
    listb.insert("end",k)
for i in mslist:
    listm.insert("end",i)
reload()
root.mainloop()


